/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstdint>
#include <vector>
#include <cmath>
#include "json_error.h"
#include "securec.h"
#include "yyjson_util.h"

using namespace json_error;

constexpr size_t BITS_PER_WORD = 64;

/**
 * resize the words buffer (expand by a multiple).
 * @param pwords
 * @param pcap
 * @param needed
 * @return
 */
static int EnsureCapacity(uint64_t **pwords, size_t *pcap, size_t needed)
{
    constexpr size_t expansionConstant = 2;

    if (*pcap >= needed) {
        return 0;
    }

    size_t newcap = (*pcap == 0) ? 4 : *pcap;
    while (newcap < needed) {
        if (newcap > SIZE_MAX / expansionConstant) {
            newcap = needed;
        } else {
            newcap *= expansionConstant;
        }
    }

    // Allocate new memory.
    uint64_t *newbuf = (uint64_t *)malloc(newcap * sizeof(uint64_t));
    if (newbuf == nullptr) {
        return -1;
    }

    // If the old memory exists, copy the existing data first.
    if (*pwords != nullptr && *pcap > 0) {
        size_t oldBytes = (*pcap) * sizeof(uint64_t);
        if (memcpy_s(newbuf, newcap * sizeof(uint64_t), *pwords, oldBytes) != EOK) {
            free(newbuf);
            return -1;
        }
        free(*pwords);
    }

    // Initialize the newly added region to 0.
    if (newcap > *pcap) {
        size_t oldWords = *pcap;
        size_t extraBytes = (newcap - oldWords) * sizeof(uint64_t);
        if (memset_s(newbuf + oldWords, extraBytes, 0, extraBytes) != EOK) {
            free(newbuf);
            return -1;
        }
    }

    *pwords = newbuf;
    *pcap = newcap;
    return 0;
}

/**
 * Preprocess the input string: remove quotes, symbols, leading zeros, and perform validity checks.
 * @param s
 * @param len
 * @param signBit
 * @return
 */
static uint8_t PreprocessRawStr(const char*& s, size_t& len, int& signBit)
{
    constexpr size_t doubleQuotationMarksLen = 2;
    if (len == 0) {
        return NAPI_INVALID_RAW_STR;
    }

    // Remove the leading and trailing double quotes.
    if (len >= doubleQuotationMarksLen && s[0] == '"' && s[len - 1] == '"') {
        s++;
        len -= doubleQuotationMarksLen;
        if (len == 0) {
            return NAPI_INVALID_RAW_STR;
        }
    }

    // Handle the sign.
    signBit = 0;
    if (*s == '+' || *s == '-') {
        if (*s == '-') {
            signBit = 1;
        }
        s++;
        len--;
        if (len == 0) {
            return NAPI_INVALID_RAW_STR;
        }
    }

    // Skip leading zeros.
    while (len > 0 && *s == '0') {
        s++;
        len--;
    }
    if (len == 0) {
        // Input consists entirely of 0s.
        return napi_ok;
    }

    // Check that all characters are digits.
    for (size_t i = 0; i < len; ++i) {
        if (s[i] < '0' || s[i] > '9') {
            return NAPI_INVALID_RAW_STR;
        }
    }
    return napi_ok;
}

static uint8_t MultiplyWordsBy10(uint64_t** words, size_t* wordCount, size_t* cap)
{
    __uint128_t carry = 0;
    for (size_t i = 0; i < *wordCount; ++i) {
        __uint128_t prod = static_cast<__uint128_t>((*words)[i]) * 10 + carry;
        (*words)[i] = static_cast<uint64_t>(prod);
        carry = prod >> BITS_PER_WORD;
    }
    while (carry) {
        if (*wordCount + 1 > *cap && EnsureCapacity(words, cap, *wordCount + 1) != 0) {
            return napi_generic_failure;
        }
        (*words)[(*wordCount)++] = static_cast<uint64_t>(carry);
        carry >>= BITS_PER_WORD;
    }
    return napi_ok;
}

static uint8_t AddDigitToWords(uint64_t** words, size_t* wordCount, size_t* cap, int digit)
{
    __uint128_t sum = static_cast<__uint128_t>((*words)[0]) + digit;
    (*words)[0] = static_cast<uint64_t>(sum);
    __uint128_t carry = sum >> BITS_PER_WORD;
    size_t i = 1;
    while (carry) {
        if (i >= *wordCount) {
            if (EnsureCapacity(words, cap, *wordCount + 1) != 0) {
                return napi_generic_failure;
            }
            (*words)[(*wordCount)++] = 0;
        }
        __uint128_t s = static_cast<__uint128_t>((*words)[i]) + carry;
        (*words)[i] = static_cast<uint64_t>(s);
        carry = s >> BITS_PER_WORD;
        ++i;
    }
    return napi_ok;
}

/**
 * Convert a decimal string → uint64_t little-endian word array.
 * @param s
 * @param len
 * @param words
 * @param wordCount
 * @return
 */
static uint8_t ConvertDecimalToWords(const char* s, size_t len, uint64_t** words, size_t* wordCount)
{
    *words = nullptr;
    size_t cap = 0;
    *wordCount = 1;

    if (EnsureCapacity(words, &cap, 1) != 0) return napi_generic_failure;
    (*words)[0] = 0;

    for (size_t idx = 0; idx < len; ++idx) {
        int digit = static_cast<unsigned char>(s[idx]) - static_cast<unsigned char>('0');
        if (MultiplyWordsBy10(words, wordCount, &cap) != napi_ok) {
            return napi_generic_failure;
        }
        if (AddDigitToWords(words, wordCount, &cap, digit) != napi_ok) {
            return napi_generic_failure;
        }
    }

    return napi_ok;
}

/**
 * Normalize the result (remove high-order zeros and check constraints).
 * @param words
 * @param wordCount
 * @return
 */
static uint8_t NormalizeWords(uint64_t* words, size_t& wordCount)
{
    while (wordCount > 1 && words[wordCount - 1] == 0) {
        wordCount--;
    }
    if (wordCount > static_cast<size_t>(INT32_MAX)) {
        return NAPI_INVALID_RAW_STR;
    }
    return napi_ok;
}

uint8_t CreateBigintFromRawStr(napi_env env, const char* rawStr, napi_value* result)
{
    if (rawStr == nullptr) {
        return NAPI_INVALID_RAW_STR;
    }

    const char* s = rawStr;
    size_t len = strlen(s);
    int signBit = 0;

    uint8_t res = PreprocessRawStr(s, len, signBit);
    if (res != napi_ok) {
        return NAPI_INVALID_RAW_STR;
    }

    // Special case: 0.
    if (len == 0) {
        uint64_t zero = 0;
        return napi_create_bigint_words(env, 0, 1, &zero, result);
    }

    uint64_t* words = nullptr;
    size_t wordCount = 0;
    res = ConvertDecimalToWords(s, len, &words, &wordCount);
    if (res != napi_ok) {
        free(words);
        return res;
    }

    res = NormalizeWords(words, wordCount);
    if (res != napi_ok) {
        free(words);
        return res;
    }

    napi_status status = napi_create_bigint_words(env, signBit, wordCount, (const uint64_t*)words, result);
    free(words);
    return status;
}

/**
 * Generate the final key, handling the special prefix for integer keys.
 * @param env
 * @param keyStr
 * @param len
 * @param allocateKeys
 * @return
 */
static const char* MakeFinalKey(napi_env env, const char* keyStr, size_t len,
                                std::vector<yyjson_util::unique_char_ptr>& allocateKeys)
{
    constexpr char utf8Prefix[] = "\x7F";
    constexpr size_t prefixLen = 1;
    size_t newKeyTotalLen = prefixLen + len + 1;

    char* newKey = new char[newKeyTotalLen];
    allocateKeys.emplace_back(newKey);
    if (memcpy_s(newKey, newKeyTotalLen, utf8Prefix, prefixLen) != EOK) {
        HandleBoundsCheckError(env, MEMCPY_S_ERROR, "memcpy_s failed while copying prefix.");
        return nullptr;
    }
    if (memcpy_s(newKey + prefixLen, newKeyTotalLen - prefixLen, keyStr, len) != EOK) {
        HandleBoundsCheckError(env, MEMCPY_S_ERROR, "memcpy_s failed while copying prefix.");
        return nullptr;
    }
    newKey[newKeyTotalLen - 1] = '\0';
    return newKey;
}

bool IsValidInteger(const char *val, size_t len)
{
    if (len == 0) {
        return false;
    }
    
    // Check the first character: if it is '0', it must be the only character (i.e., "0" is valid).
    if (val[0] == '0') {
        return len == 1;
    }
    
    for (size_t i = 0; i < len; i++) {
        if (val[i] < '0' || val[i] > '9') {
            return false;
        }
    }
    return true;
}

napi_value YYJSONObjectToNapi(napi_env env, yyjson_val *val, size_t mode)
{
    napi_value result = nullptr;
    size_t count = unsafe_yyjson_get_len(val);
    
    std::vector<const char*> keysVec;
    std::vector<napi_value> valuesVec;
    std::vector<yyjson_util::unique_char_ptr> allocateKeys;
    keysVec.reserve(count);
    valuesVec.reserve(count);
    
    size_t idx;
    size_t max;
    yyjson_val *key;
    yyjson_val *value;
    if (LIKELY(mode == DEFAULT)) {
        yyjson_obj_foreach(val, idx, max, key, value) {
            const char* keyStr = unsafe_yyjson_get_str(key);
            size_t len = unsafe_yyjson_get_len(key);
            const char* finalKey = keyStr;
            if (UNLIKELY(IsValidInteger(keyStr, len))) {
                finalKey = MakeFinalKey(env, keyStr, len, allocateKeys);
            }
            keysVec.push_back(finalKey);
            valuesVec.push_back(yyjson_util::YYJSONToNapi(env, value, mode));
        }
        HANDLE_NAPI_ERROR(env, napi_create_object_with_named_properties(env, &result, keysVec.size(), keysVec.data(),
                                                                    valuesVec.data()));
    } else {
        HANDLE_NAPI_ERROR(env, napi_create_object(env, &result));
        yyjson_obj_foreach(val, idx, max, key, value) {
            const char* keyStr = unsafe_yyjson_get_str(key);
            napi_value napiValue = yyjson_util::YYJSONToNapi(env, value, mode);
            HANDLE_NAPI_ERROR(env, napi_set_named_property(env, result, keyStr, napiValue));
        }
    }
    return result;
}

napi_value YYJSONArrayToNapi(napi_env env, yyjson_val *val, size_t mode)
{
    napi_value result = nullptr;
    HANDLE_NAPI_ERROR(env, napi_create_array(env, &result));
    size_t idx;
    size_t max;
    yyjson_val *value;
    yyjson_arr_foreach(val, idx, max, value) {
        napi_value res = yyjson_util::YYJSONToNapi(env, value, mode);
        HANDLE_NAPI_ERROR(env, napi_set_element(env, result, idx, res));
    }
    return result;
}

napi_value yyjson_util::YYJSONToNapi(napi_env env, yyjson_val *val, size_t mode)
{
    napi_value result = nullptr;
    if (yyjson_is_obj(val)) {
        return YYJSONObjectToNapi(env, val, mode);
    } else if (yyjson_is_arr(val)) {
        return YYJSONArrayToNapi(env, val, mode);
    } else if (yyjson_is_str(val)) {
        HANDLE_NAPI_ERROR(env, napi_create_string_utf8(env, unsafe_yyjson_get_str(val), NAPI_AUTO_LENGTH, &result));
    } else if (yyjson_is_uint(val)) {
        uint64_t uintVal = unsafe_yyjson_get_uint(val);
        if (uintVal <= numberMaxValue) {
            HANDLE_NAPI_ERROR(env, napi_create_int64(env, static_cast<int64_t>(uintVal), &result));
        } else {
            HANDLE_NAPI_ERROR(env, napi_create_bigint_uint64(env, uintVal, &result));
        }
    } else if (yyjson_is_sint(val)) {
        int64_t sintVal = unsafe_yyjson_get_sint(val);
        if (sintVal >= numberMinValue && sintVal <= numberMaxValue) {
            HANDLE_NAPI_ERROR(env, napi_create_int64(env, sintVal, &result));
        } else {
            HANDLE_NAPI_ERROR(env, napi_create_bigint_int64(env, sintVal, &result));
        }
    } else if (yyjson_is_num(val)) {
        HANDLE_NAPI_ERROR(env, napi_create_double(env, unsafe_yyjson_get_num(val), &result));
    } else if (yyjson_is_bool(val)) {
        return unsafe_yyjson_get_bool(val) ? g_napiTrue : g_napiFalse;
    } else if (yyjson_is_raw(val)) {
        HANDLE_NAPI_ERROR(env, CreateBigintFromRawStr(env, unsafe_yyjson_get_raw(val), &result));
    } else if (yyjson_is_null(val)) {
        HANDLE_NAPI_ERROR(env, napi_get_null(env, &result));
    }
    return result;
}

yyjson_mut_val* NapiObjectToYYJSON(napi_env env, yyjson_mut_doc* doc, napi_value value)
{
    // Assume the key length will not exceed 64 bytes to avoid a second pass.
    constexpr size_t maxKeyLen = 64;
    
    yyjson_mut_val* obj = yyjson_mut_obj(doc);
    
    napi_value properties;
    HANDLE_NAPI_ERROR(env, napi_get_property_names(env, value, &properties));
    uint32_t length;
    HANDLE_NAPI_ERROR(env, napi_get_array_length(env, properties, &length));

    for (uint32_t i = 0; i < length; i++) {
        napi_value key;
        HANDLE_NAPI_ERROR(env, napi_get_element(env, properties, i, &key));
        
        yyjson_util::unique_char_ptr keyBuf(new char[maxKeyLen + 1]);
        size_t keyLen = 0;
        HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, key, keyBuf.get(), maxKeyLen + 1, &keyLen));
        if (UNLIKELY(keyLen >= maxKeyLen)) {
            HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, key, nullptr, 0, &keyLen));
            keyBuf.reset(new char[keyLen + 1]);
            HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, key, keyBuf.get(), keyLen + 1, &keyLen));
        }

        napi_value propValue;
        HANDLE_NAPI_ERROR(env, napi_get_property(env, value, key, &propValue));
        yyjson_mut_val* val = yyjson_util::NapiToYYJSON(env, doc, propValue);
        yyjson_mut_obj_add(obj, yyjson_mut_strncpy(doc, keyBuf.get(), keyLen), val);
    }
    return obj;
}

yyjson_mut_val* NapiArrayToYYJSON(napi_env env, yyjson_mut_doc* doc, napi_value value)
{
    uint32_t length;
    HANDLE_NAPI_ERROR(env, napi_get_array_length(env, value, &length));
    yyjson_mut_val* arr = yyjson_mut_arr(doc);
    
    for (uint32_t i = 0; i < length; i++) {
        napi_value element;
        HANDLE_NAPI_ERROR(env, napi_get_element(env, value, i, &element));
        yyjson_mut_val* val = yyjson_util::NapiToYYJSON(env, doc, element);
        yyjson_mut_arr_append(arr, val);
    }
    return arr;
}

yyjson_mut_val* NapiStringToYYJSON(napi_env env, yyjson_mut_doc* doc, napi_value value)
{
    // Assume the string length will not exceed 512 bytes to avoid a second pass.
    constexpr size_t maxStrLen = 512;
    yyjson_util::unique_char_ptr buf(new char[maxStrLen + 1]);
    size_t len;
    HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, value, buf.get(), maxStrLen + 1, &len));
    if (UNLIKELY(len >= maxStrLen)) {
        // If the length exceeds the preset limit, read again.
        HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, value, nullptr, 0, &len));
        buf.reset(new char[len + 1]);
        HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, value, buf.get(), len + 1, &len));
    }
    return yyjson_mut_strncpy(doc, buf.get(), len);
}

yyjson_mut_val* NapiNumberToYYJSON(napi_env env, yyjson_mut_doc* doc, napi_value value)
{
    double num;
    HANDLE_NAPI_ERROR(env, napi_get_value_double(env, value, &num));
    // Determine whether it is an integer (i.e., the fractional part is 0).
    if (std::floor(num) == num) {
        // Check whether it is within the long long range.
        if (LIKELY(num >= LONG_LONG_MIN && num <= LONG_LONG_MAX)) {
            return yyjson_mut_sint(doc, static_cast<long long>(num));
        } else {
            // If it exceeds the integer range, store it as a double only.
            return yyjson_mut_real(doc, num);
        }
    } else {
        return yyjson_mut_real(doc, num);
    }
}

yyjson_mut_val* NapiBigIntToYYJSON(napi_env env, yyjson_mut_doc* doc, napi_value value)
{
    // Assume the bignumber length will not exceed 256 to avoid a second pass.
    constexpr size_t maxBigNumberLen = 256;
    bool lossless;
    int64_t i64;
    HANDLE_NAPI_ERROR(env, napi_get_value_bigint_int64(env, value, &i64, &lossless));
    if (LIKELY(lossless)) {
        return yyjson_mut_sint(doc, i64);
    } else {
        // If not lossless or conversion failed, convert to string representation.
        napi_value strValue;
        HANDLE_NAPI_ERROR(env, napi_coerce_to_string(env, value, &strValue));
        yyjson_util::unique_char_ptr buf(new char[maxBigNumberLen + 1]);
        size_t len = 0;
        HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, strValue, buf.get(), maxBigNumberLen + 1, &len));
        if (UNLIKELY(len >= maxBigNumberLen)) {
            // If the length exceeds the preset limit, read again.
            HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, strValue, nullptr, 0, &len));
            buf.reset(new char[len + 1]);
            HANDLE_NAPI_ERROR(env, napi_get_value_string_utf8(env, strValue, buf.get(), len + 1, &len));
        }
        return yyjson_mut_strncpy(doc, buf.get(), len);
    }
}

yyjson_mut_val* yyjson_util::NapiToYYJSON(napi_env env, yyjson_mut_doc* doc, napi_value value)
{
    napi_valuetype type;
    HANDLE_NAPI_ERROR(env, napi_typeof(env, value, &type));
    if (type == napi_object) {
        bool isArray;
        HANDLE_NAPI_ERROR(env, napi_is_array(env, value, &isArray));
        if (!isArray) {
            return NapiObjectToYYJSON(env, doc, value);
        } else {
            return NapiArrayToYYJSON(env, doc, value);
        }
    } else if (type == napi_string) {
        return NapiStringToYYJSON(env, doc, value);
    } else if (type == napi_number) {
        return NapiNumberToYYJSON(env, doc, value);
    } else if (type == napi_bigint) {
        return NapiBigIntToYYJSON(env, doc, value);
    } else if (type == napi_boolean) {
        bool b;
        HANDLE_NAPI_ERROR(env, napi_get_value_bool(env, value, &b));
        return yyjson_mut_bool(doc, b);
    }
    return yyjson_mut_null(doc);
}

size_t yyjson_util::GetBlockSize(size_t byteLength)
{
    // Test 10MB, 100MB and 200MB files with different BLOCK_SIZE values;
    // the minimum time is roughly around 10–20 iterations.
    //          100kb   500kb   1mb     10mb    100mb   200mb
    // 10MB     0.211   0.204   0.215   0.219
    // 100MB    1.629   1.635   1.604   1.595   1.605
    // 200MB    2.817   2.873   2.826   2.788   2.826   2.802
    constexpr size_t roundTime = 20;
    size_t blockSize;
    if (byteLength <= roundTime) {
        blockSize = byteLength;
    } else {
        blockSize = byteLength / (roundTime - 1);
    }
    return blockSize;
}

void yyjson_util::ParseIncAsync(napi_env env, void* data)
{
    AsyncData* asyncData = static_cast<AsyncData*>(data);
    yyjson_read_flag flg = asyncData->flag | GetDefaultReadFlag();
    yyjson_util::unique_yyjson_incr_state state(yyjson_incr_new(asyncData->data,
        asyncData->byteLength, flg, nullptr));

    if (state.get() == nullptr) {
        asyncData->errorMsg = YYJSONErrorMessages.find(YYJSON_CREATE_STATE_FAILED)->second;
        return;
    }

    yyjson_read_err err;
    size_t readSoFar = 0;
    size_t blockSize = yyjson_util::GetBlockSize(asyncData->byteLength);
    yyjson_doc* doc = nullptr;
    do {
        readSoFar += blockSize;
        if (readSoFar > asyncData->byteLength) {
            readSoFar = asyncData->byteLength;
        }
        doc = yyjson_incr_read(state.get(), readSoFar, &err);
        if (err.code == YYJSON_READ_SUCCESS) {
            break;
        }
        if (err.code != YYJSON_READ_ERROR_MORE) {
            asyncData->errorMsg = GetErrorMsgByYYJSONReadErr(asyncData->data, asyncData->byteLength, err);
            return;
        }
    } while (readSoFar < asyncData->byteLength);

    if (doc == nullptr) {
        asyncData->errorMsg = GetErrorMsgByYYJSONReadErr(asyncData->data, asyncData->byteLength, err);
        return;
    }

    asyncData->doc = doc;
    asyncData->success = true;
}

inline void RejectPromiseWithError(napi_env env, napi_deferred deferred, const std::string& errorMsg)
{
    napi_value error_msg;
    napi_create_string_utf8(env, errorMsg.c_str(), errorMsg.length(), &error_msg);

    napi_value error;
    napi_create_error(env, nullptr, error_msg, &error);

    napi_reject_deferred(env, deferred, error);
}

void yyjson_util::ParseIncAsyncCompleted(napi_env env, napi_status status, void* data)
{
    AsyncData* asyncData = static_cast<AsyncData*>(data);
    if (asyncData->success && asyncData->doc != nullptr) {
        yyjson_val* root = yyjson_doc_get_root(asyncData->doc);
        napi_value result = YYJSONToNapi(env, root, DEFAULT);
        napi_resolve_deferred(env, asyncData->deferred, result);
    } else {
        RejectPromiseWithError(env, asyncData->deferred, asyncData->errorMsg);
    }
    yyjson_doc_free(asyncData->doc);
    delete asyncData;
}

void yyjson_util::ParseJsonNodeAsyncCompleted(napi_env env, napi_status status, void* data)
{
    AsyncData* asyncData = static_cast<AsyncData*>(data);
    if (asyncData->success && asyncData->doc != nullptr) {
        (asyncData->obj)->root_ = yyjson_doc_get_root(asyncData->doc);
        (asyncData->obj)->doc_ = asyncData->doc;
        napi_value undefinedValue;
        napi_get_undefined(env, &undefinedValue);
        napi_resolve_deferred(env, asyncData->deferred, undefinedValue);
    } else {
        RejectPromiseWithError(env, asyncData->deferred, asyncData->errorMsg);
    }
    delete asyncData;
}

yyjson_read_flag yyjson_util::GetDefaultReadFlag()
{
    return YYJSON_READ_NOFLAG |
            // Allow a trailing comma after the last element of an object or array.
            YYJSON_READ_ALLOW_TRAILING_COMMAS |
            // Allow recognition of 1e999, NaN, inf, and -Infinity.
            YYJSON_READ_ALLOW_INF_AND_NAN |
            // Allow numbers that exceed the double range to be represented as raw strings.
            YYJSON_READ_BIGNUM_AS_RAW;
}

yyjson_write_flag yyjson_util::GetDefaultWriteFlag()
{
    return YYJSON_WRITE_NOFLAG |
            // Support serialization of NaN and Infinity.
            YYJSON_WRITE_ALLOW_INF_AND_NAN;
}